#include "VolumeRenderPCH.h"
#include "LLOITBuildListsPass.h"
#include "DVRMethodsDef.h"

const int MAX_LIST_NODE = WINDOW_KEYWORD::getWindowWidth() * WINDOW_KEYWORD::getWindowHeight() * 8;

struct SListNode
{
	unsigned Next;
	unsigned PackedColor;
	unsigned Depth;
};

CLLOITBuildListsPass::~CLLOITBuildListsPass()
{
	glDeleteBuffers(1, &m_ListAtomicCounter);
	glDeleteBuffers(1, &m_ListSSBO);
}

//**************************************************************************************************
//FUNCTION:
void CLLOITBuildListsPass::initV()
{
	m_pCube = std::dynamic_pointer_cast<CCube>(ResourceManager::getGameObjectByName("Cube"));
	m_pVolumeObj = std::dynamic_pointer_cast<CVolumeObj>(ResourceManager::getGameObjectByName("Volume"));
	m_pTFObj = std::dynamic_pointer_cast<CTFObj>(ResourceManager::getGameObjectByName("TF"));

	m_pHeadPtrTex = __createHeadPtrTex2D();
	genTexture(m_pHeadPtrTex);
	m_ClearImageFBO = genFBO({ m_pHeadPtrTex });

	GLuint Zero = 0;
	m_ListAtomicCounter = genBuffer(GL_ATOMIC_COUNTER_BUFFER, sizeof(GLuint), &Zero, GL_DYNAMIC_DRAW, 0);

	//copied from sxf, i dont know why use GL_SHADER_STORAGE_BUFFER instead of GL_TEXTURE_BUFFER.
	m_ListSSBO = genBuffer(GL_SHADER_STORAGE_BUFFER, MAX_LIST_NODE * sizeof(SListNode), nullptr, GL_DYNAMIC_DRAW, 0);

	m_pOccBricks = std::make_shared<hiveDVR::COccupancyBrick>();
	m_pOccBricks->init(m_pVolumeObj->getPtrOfVolume());
	m_pCube->use();
	__genInstanceBuffer();

	m_pShader = std::make_shared<CShader>("shaders/LLOIT_BuildList_VS.vert", "shaders/LLOIT_BuildList_FS.frag");
	m_pShader->activeShader();

	auto BrickSize = m_pOccBricks->getNormalizedBoxSize();
	m_pShader->setFloatUniformValue("u_BrickSize", BrickSize.x, BrickSize.y, BrickSize.z);
	m_pShader->setIntUniformValue("u_MaxListNodes", MAX_LIST_NODE);
	//same as DVRPass.
	m_pShader->setIntUniformValue("u_UseGradient", m_pTFObj->isUsingGradient());
	m_pShader->setFloatUniformValue("u_SamplingFactor", ResourceManager::getSharedDataByName<float>("SamplingFactor"));
	m_pShader->setTextureUniformValue("u_TransferFunctionTex", m_pTFObj->getTransferFuncTex());
	m_pShader->setTextureUniformValue("u_VolumeTex", m_pVolumeObj->getVolumeTex());
}

//**************************************************************************************************
//FUNCTION:
void CLLOITBuildListsPass::updateV()
{
#ifdef USING_ALL_METHODS
	if (ResourceManager::getSharedDataByName<EDVRMethod>("DVRMethod") == EDVRMethod::LINKED_LIST_OIT)
#endif
	{
		//clear head ptr image.
		glBindFramebuffer(GL_FRAMEBUFFER, m_ClearImageFBO);
		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		glBindFramebuffer(GL_FRAMEBUFFER, 0);

		//generate linked list
		glEnable(GL_CULL_FACE);
		glCullFace(GL_BACK);

		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		m_pShader->activeShader();
		m_pShader->setIntUniformValue("u_UseGradient", m_pTFObj->isUsingGradient());
		m_pShader->setFloatUniformValue("u_SamplingFactor", ResourceManager::getSharedDataByName<float>("SamplingFactor"));

		auto CubeMatrix = m_pCube->getModelMatrix();
		m_pShader->setMat4UniformValue("u_ModelMatrix", glm::value_ptr(CubeMatrix));
		glm::vec3 CameraInModel = glm::inverse(glm::mat4(glm::scale(CubeMatrix, glm::vec3(1.0)))) * glm::vec4(Camera::getMainCameraPos(), 1.0f);
		std::cout << CameraInModel << std::endl;
		m_pShader->setFloatUniformValue("u_CameraPosInModel", CameraInModel.x, CameraInModel.y, CameraInModel.z);

		__resetListAtomicCounter();

		//TODO: draw element
		m_pCube->use();
		glDrawArraysInstanced(GL_TRIANGLES, 0, 36, m_pOccBricks->getSize());
		glBindVertexArray(0);

		glDisable(GL_CULL_FACE);
	}
}

//**************************************************************************************************
//FUNCTION:
std::shared_ptr<hiveGraphics::STexture> CLLOITBuildListsPass::__createHeadPtrTex2D()
{
	auto pHeadPtrTex = std::make_shared<STexture>();
	pHeadPtrTex->InternalFormat = GL_R32UI;
	pHeadPtrTex->ExternalFormat = GL_RED_INTEGER;
	pHeadPtrTex->DataType = GL_UNSIGNED_INT;
	pHeadPtrTex->Type4MinFilter = GL_NEAREST;
	pHeadPtrTex->Type4MagFilter = GL_NEAREST;
	pHeadPtrTex->isMipmap = GL_FALSE;
	pHeadPtrTex->ImageBindUnit = 0;

	return pHeadPtrTex;
}

//**************************************************************************************************
//FUNCTION:
void CLLOITBuildListsPass::__resetListAtomicCounter()
{
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, m_ListAtomicCounter);
	GLuint Zero = 0;
	glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint), &Zero);
}

//**************************************************************************************************
//FUNCTION:
void CLLOITBuildListsPass::__genInstanceBuffer()
{
	auto NumBricks = m_pOccBricks->getSize();
	m_InstanceBuffer = genBuffer(GL_ARRAY_BUFFER, NumBricks * sizeof(glm::mat4), m_pOccBricks->getPtrOfTransformSet(), GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, m_InstanceBuffer);
	for (size_t i = 0; i < NumBricks; ++i)
	{
		// set attribute pointers for matrix (4 times vec4)
		glEnableVertexAttribArray(1);
		glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);
		glEnableVertexAttribArray(2);
		glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4)));
		glEnableVertexAttribArray(3);
		glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(2 * sizeof(glm::vec4)));
		glEnableVertexAttribArray(4);
		glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(3 * sizeof(glm::vec4)));

		glVertexAttribDivisor(1, 1);
		glVertexAttribDivisor(2, 1);
		glVertexAttribDivisor(3, 1);
		glVertexAttribDivisor(4, 1);

	}
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}
